package net.es.weathermap.http;

import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.es.weathermap.beans.AggrConnection;
import net.es.weathermap.beans.Connection;
import net.es.weathermap.beans.Domain;
import net.es.weathermap.beans.Hub;
import net.es.weathermap.beans.IfceConnection;
import net.es.weathermap.beans.Node;
import net.es.weathermap.beans.Path;
import net.es.weathermap.metric.MetricProvider;
import net.es.weathermap.metric.MetricProviderFactory;
import net.es.weathermap.metric.RandomUtilizationMP;
import net.es.weathermap.metric.Timestep;
import net.es.weathermap.path.idc.IDCPathProvider;
import net.es.weathermap.topo.TopologyProvider;
import net.es.weathermap.topo.ps.PerfsonarTP;

import org.json.JSONException;
import org.json.JSONObject;

import org.apache.log4j.*;

public class JSONServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static MetricProvider bpsmp;
    private static MetricProvider resvmp;
    private static Logger LOG = Logger.getLogger(JSONServlet.class);
    private static TopologyProvider tp;
    public void init() {
        LOG.info("init.start");
        tp = PerfsonarTP.getInstance();
        resvmp = MetricProviderFactory.getResvMP(tp.getTopology());
        bpsmp = MetricProviderFactory.getUtilMP(tp.getTopology());
        LOG.info("init.finish");
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        response.setStatus(HttpServletResponse.SC_OK);


        String action = request.getParameter("action");

        if (action == null || action.trim().equals("")) {
            action = "getTopology";
        }
        if (tp.getTopology() == null) {
            LOG.warn("servlet init failed");
        }
        LOG.info("JSONServlet.start: " + action);
        if (action.equals("getTopology")) {
            JSONObject topologyResponseJ;
            PrintWriter pw = response.getWriter();
            try {
                String topologyRequest = request.getParameter("topologyRequest");
                JSONObject topologyRequestJ = null;
                if (topologyRequest != null && !topologyRequest.equals("")) {
                    topologyRequestJ = new JSONObject(topologyRequest);
                }
                TopologyHandler th = new TopologyHandler();
                topologyResponseJ = th.getTopology(tp.getTopology(), topologyRequestJ);
                String output = topologyResponseJ.toString(2);
                pw.println(output);
            } catch (JSONException e) {
                pw.println("Error: " + e.getMessage());
                e.printStackTrace(pw);
            }
        } else if (action.equals("getMetrics")) {
            String metricsRequest = request.getParameter("metricsRequest");
            JSONObject metricsRequestJ;
            PrintWriter pw = response.getWriter();
            try {
                if (metricsRequest != null) {
                    metricsRequestJ = new JSONObject(metricsRequest);
                } else {
                    metricsRequestJ = new JSONObject();
                }
                String output = this.getMetrics(metricsRequestJ).toString(2);
                pw.println(output);
            } catch (JSONException e) {
                pw.println("Error: " + e.getMessage());
                e.printStackTrace(pw);
            } catch (Exception e) {
                pw.println("Error: " + e.getMessage());
                e.printStackTrace();
            }
        } else if (action.equals("getPaths")) {
            String pathsRequest = request.getParameter("pathsRequest");
            JSONObject pathsRequestJ;
            PrintWriter pw = response.getWriter();
            try {
                if (pathsRequest != null) {
                    pathsRequestJ = new JSONObject(pathsRequest);
                } else {
                    pathsRequestJ = new JSONObject();
                }
                String output = this.getPaths().toString(2);
                pw.println(output);
            } catch (JSONException e) {
                pw.println("Error: " + e.getMessage());
                e.printStackTrace(pw);
            } catch (Exception e) {
                pw.println("Error: " + e.getMessage());
                e.printStackTrace();
            }
        } else if (action.equals("getPlayback")) {
            String historyRequest = request.getParameter("historyRequest");
            JSONObject historyRequestJ;
            PrintWriter pw = response.getWriter();
            try {
                if (historyRequest != null) {
                    historyRequestJ = new JSONObject(historyRequest);
                } else {
                    historyRequestJ = new JSONObject();
                }
                String output = this.getHistory(historyRequestJ).toString(2);
                pw.println(output);
            } catch (JSONException e) {
                pw.println("Error: " + e.getMessage());
                e.printStackTrace(pw);
            } catch (Exception e) {
                pw.println("Error: " + e.getMessage());
                e.printStackTrace();
            }
        } else {
            PrintWriter pw = response.getWriter();
            pw.println("Error: Unknown action parameter");

        }
        LOG.info("JSONServlet.finish: " + action);
    }

    private JSONObject getPaths() throws JSONException {
        JSONObject pathResponseJ = new JSONObject();
        IDCPathProvider ip = IDCPathProvider.getInstance();
        List<Path> paths = ip.getPaths(tp.getTopology());
        for (Path path : paths) {
            JSONObject pathJ = new JSONObject();
            Integer bw = path.getBandwidth();
            String description = path.getDescription();
            pathJ.put("id", path.getTopoId());
            pathJ.put("bandwidth", bw);
            pathJ.put("description", description);
            JSONObject hopsJ = new JSONObject();
            ArrayList<Hub> hubs = new ArrayList<Hub>();
            for (Node node : path.getHops()) {
                hopsJ.append("lod_1", node.getTopoId());
                hopsJ.append("lod_2", node.getTopoId());
                for (Hub hub : tp.getTopology().getHubs()) {
                    for (Node hubNode: hub.getNodes()) {
                        if (hubNode.equals(node)) {
                            if (!hubs.contains(hub)) {
                                hubs.add(hub);
                            }
                        }
                    }
                }
            }
            for (Hub hub : hubs) {
                hopsJ.append("lod_0", hub.getTopoId());
            }
            pathJ.put("hops", hopsJ);
            pathResponseJ.put(path.getTopoId(), pathJ);
            Node node = path.getSrc();
            for (Hub hub : tp.getTopology().getHubs()) {
                for (Node hubNode : hub.getNodes()) {
                    if (hubNode.equals(node)) {
                        pathJ.put("group", hub);
                    }
                }
            }
        }
        return pathResponseJ;
    }

    private JSONObject getMetrics(JSONObject metricsRequestJ) throws Exception {

        LOG.info("getMetrics.start");
        boolean useMbps = false;
        Boolean usePercentReserved = false;
        HashMap<String, Object> options = new HashMap<String,Object>();
        MetricProvider mp = bpsmp;
        if (metricsRequestJ.has("reservations")) {
            LOG.info("using percent reserved");
            usePercentReserved = true;
            mp = resvmp;
        } else if (metricsRequestJ.has("mbps")) {
            LOG.info("using mbps");
            useMbps = true;
        } else {
            LOG.info("using percent");
        }
        options.put("usePercentReserved", (Object) usePercentReserved);
        String metricType = "average";
        if (metricsRequestJ.has("metricType")) {
            metricType = metricsRequestJ.getString("metricType");
        }
        options.put("metricType", (Object) metricType);
        // 30 seconds
        int aggrSeconds = 30;
        if (metricsRequestJ.has("aggrSeconds")) {
            aggrSeconds = (int) metricsRequestJ.getInt("aggrSeconds");
        }
        options.put("aggrSeconds", (Object) aggrSeconds);
        // if seqId is not present, this request is ignored
        int seqId = 0;
        if (metricsRequestJ.has("seqId")) {
            seqId = (int) metricsRequestJ.getInt("seqId");
        }
        JSONObject metricsResponseJ = new JSONObject();
        metricsResponseJ.put("seqId", seqId);
        if (useMbps) {
            metricsResponseJ.put("utilizationType", "Mbps");
        } else {
            metricsResponseJ.put("utilizationType", "percent");
        }
        JSONObject metricsResponseDataJ = new JSONObject();
        metricsResponseJ.put("utilizationData", metricsResponseDataJ);
        DecimalFormat df = new DecimalFormat("##.####");

        List<Timestep> frames = mp.getValues("utilization", null, options);
        Timestep frame = frames.get(0);
        HashMap<String, HashMap<String, Double>> metricsResp = frame.getTimestep();
        Iterator<String> ifceNameIt = metricsResp.keySet().iterator();
        while (ifceNameIt.hasNext()) {
            String ifceName = ifceNameIt.next();
            HashMap<String, Double> metricsInfo = metricsResp.get(ifceName);
            JSONObject ifceStatsJ = new JSONObject();
            JSONObject inJ = new JSONObject();
            if (!useMbps) {
                try {
                    inJ.put("utilization", Double.valueOf(df.format(metricsInfo.get("utilization_in"))));
                } catch (NumberFormatException e) {
                    inJ.put("utilization", 0.0D);
                }
            } else {
                try {
                    inJ.put("utilization", Double.valueOf(df.format(metricsInfo.get("mbps_in"))));
                    //LOG.info(Double.valueOf(df.format(metricsInfo.get("mbps_in"))));
                } catch (NumberFormatException e) {
                    LOG.info("caught NumberFormatException");
                    inJ.put("utilization", 0.0D);
                }
            }
            try {
                inJ.put("capacity", Double.valueOf(df.format(metricsInfo.get("capacity"))));
            } catch (NumberFormatException e) {
                inJ.put("capacity", 0.0D);
            }
            ifceStatsJ.put("in", inJ);
            JSONObject outJ = new JSONObject();

            if (!useMbps) {
                try {
                    outJ.put("utilization", Double.valueOf(df.format(metricsInfo.get("utilization_out"))));
                } catch (NumberFormatException e) {
                    outJ.put("utilization", 0.0);
                }
            } else {
                try {
                    outJ.put("utilization", Double.valueOf(df.format(metricsInfo.get("mbps_out"))));
                } catch (NumberFormatException e) {
                    outJ.put("utilization", 0.0);
                }
            }
            ifceStatsJ.put("out", outJ);
            metricsResponseDataJ.put(ifceName, ifceStatsJ);
        }
        LOG.info("getMetrics.finish");
        return metricsResponseJ;
    }

    private JSONObject getHistory(JSONObject historyRequestJ) throws Exception {

        LOG.info("getHistory.start");
        int timelineCtr = 0;
        boolean useMbps = false;
        // percent utilization not available with history
        if (historyRequestJ.has("mbps")) {
            LOG.info("using mbps");
            useMbps = true;
        } else {
            LOG.info("using percent");
        }
        // add header
        JSONObject historyResponseJ = new JSONObject();
        historyResponseJ.put("dataset", "timeline");
        JSONObject attrsJ = new JSONObject();
        historyResponseJ.put("timelineAttrs", attrsJ);
        if (useMbps) {
            attrsJ.put("utilizationType", "Mbps");
        } else {
            attrsJ.put("utilizationType", "percent");
        }
        // October 1, 2009 at noon
        int startSeconds = 1254423600;
        if (historyRequestJ.has("startSeconds")) {
            startSeconds = (int) historyRequestJ.getInt("startSeconds");
        }
        attrsJ.put("startSeconds", startSeconds);
        // October 2, 2009 at noon
        int endSeconds = 1254510000;
        if (historyRequestJ.has("endSeconds")) {
            endSeconds = (int) historyRequestJ.getInt("endSeconds");
        }
        attrsJ.put("endSeconds", endSeconds);
        // four minutes
        int resolution = 240;
        if (historyRequestJ.has("aggrSeconds")) {
            resolution = (int) historyRequestJ.getInt("aggrSeconds");
        }
        attrsJ.put("resolution", resolution);

        HashMap<String, Object> options = new HashMap<String,Object>();
        MetricProvider mp = bpsmp;
        String metricType = "average";
        if (historyRequestJ.has("metricType")) {
            metricType = historyRequestJ.getString("metricType");
        }
        LOG.info("start: " + startSeconds + ", end: " + endSeconds +
                 ", resolution: " + resolution);
        int timesteps = (endSeconds - startSeconds) / resolution;
        LOG.info("servlet timesteps: " + timesteps);
        options.put("metricType", (Object) metricType);
        options.put("startSeconds", (Object) startSeconds);
        options.put("endSeconds", (Object) endSeconds);
        options.put("aggrSeconds", (Object) resolution);
        options.put("timesteps", (Object) timesteps);

        List<Timestep> frames = mp.getValues("utilization", null, options);
        int ctr = 0;
        for (int i=startSeconds; i< endSeconds; i += resolution) {
            Timestep frame = frames.get(ctr);
            HashMap<String, HashMap<String, Double>> historyResp =
                frame.getTimestep();
            JSONObject historyResponseDataJ = new JSONObject();
            historyResponseDataJ.put("timestamp", i);
            JSONObject metricResponseDataJ = new JSONObject();
            historyResponseDataJ.put("metrics", metricResponseDataJ);
            historyResponseJ.append("timelineData", historyResponseDataJ);
            DecimalFormat df = new DecimalFormat("##.####");

            Iterator<String> ifceNameIt = historyResp.keySet().iterator();
            while (ifceNameIt.hasNext()) {
                String ifceName = ifceNameIt.next();
                HashMap<String, Double> metricsInfo = historyResp.get(ifceName);
                JSONObject ifceStatsJ = new JSONObject();
                JSONObject inJ = new JSONObject();
                if (!useMbps) {
                    try {
                        inJ.put("utilization", Double.valueOf(df.format(metricsInfo.get("utilization_in"))));
                    } catch (NumberFormatException e) {
                        inJ.put("utilization", 0.0D);
                    }
                } else {
                    try {
                        inJ.put("utilization", Double.valueOf(df.format(metricsInfo.get("mbps_in"))));
                    } catch (NumberFormatException e) {
                        inJ.put("utilization", 0.0D);
                    }
                }
                try {
                    inJ.put("capacity", Double.valueOf(df.format(metricsInfo.get("capacity"))));
                } catch (NumberFormatException e) {
                    inJ.put("capacity", 0.0D);
                }
                ifceStatsJ.put("in", inJ);
                JSONObject outJ = new JSONObject();

                if (!useMbps) {
                    try {
                        outJ.put("utilization", Double.valueOf(df.format(metricsInfo.get("utilization_out"))));
                    } catch (NumberFormatException e) {
                        outJ.put("utilization", 0.0);
                    }
                } else {
                    try {
                        outJ.put("utilization", Double.valueOf(df.format(metricsInfo.get("mbps_out"))));
                    } catch (NumberFormatException e) {
                        outJ.put("utilization", 0.0);
                    }
                }
                ifceStatsJ.put("out", outJ);
                metricResponseDataJ.put(ifceName, ifceStatsJ);
            }
            ctr++;
        }
        LOG.info("getHistory.finish");
        return historyResponseJ;
    }
}
